/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { CallGraph, CallSite } from '../../callgraph/model/CallGraph';
import { stmtIsCopyKind, stmtIsCreateAddressObj, stmtIsReadKind, stmtIsWriteKind } from '../../callgraph/pointerAnalysis/PTAUtils';
import { Constant } from '../../core/base/Constant';
import { AbstractExpr, AbstractInvokeExpr } from '../../core/base/Expr';
import { Local } from '../../core/base/Local';
import { AbstractFieldRef, ArkParameterRef, ClosureFieldRef } from '../../core/base/Ref';
import { ArkAssignStmt, ArkInvokeStmt, ArkIfStmt, ArkReturnStmt, ArkThrowStmt, Stmt, ArkReturnVoidStmt, ArkAliasTypeDefineStmt } from '../../core/base/Stmt';
import { Trap } from '../../core/base/Trap';
import { Value } from '../../core/base/Value';
import { DummyStmt } from '../../core/common/ArkIRTransformer';
import { MFPDataFlowSolver } from '../../core/dataflow/GenericDataFlow';
import { ReachingDefFlowGraph, ReachingDefProblem } from '../../core/dataflow/ReachingDef';
import { NodeID } from '../../core/graph/BaseExplicitGraph';
import { ArkMethod } from '../../core/model/ArkMethod';
import { FieldSignature } from '../../core/model/ArkSignature';
import { Scene } from '../../Scene';
import { SparseBitVector } from '../../utils/SparseBitVector';
import { DVFG, VFGEdgeKind } from '../DVFG';
import {
    ActualClosureParmVFGNode,
    ActualParmVFGNode,
    ActualReturnVFGNode,
    AddrObjVFGNode,
    AssignVFGNode,
    CmpVFGNode,
    CopyVFGNode,
    DVFGNode,
    FormalClosureParmVFGNode,
    FormalParmVFGNode,
    FormalReturnVFGNode,
    LoadVFGNode,
    ThrowVFGNode,
    VFGNodeKind,
    WriteVFGNode,
} from '../VFGNode';
import { BasicBlock } from '../../core/graph/BasicBlock';
import { LexicalEnvType } from '../../core/base/Type';

/* 
 * Class for constructing Direct Value Flow Graph (DVFG) 
 * Responsible for intra-procedural and inter-procedural data flow analysis without PTA results
 */
export class DVFGBuilder {
    private dvfg: DVFG;
    private scene: Scene;
    private cg: CallGraph;
    private method2ReturnNodeMap: Map<NodeID, SparseBitVector>; // Maps methods to their return nodes

    constructor(dvfg: DVFG, s: Scene, cg: CallGraph) {
        this.dvfg = dvfg;
        this.scene = s;
        this.cg = cg;
        this.method2ReturnNodeMap = new Map();
    }

    /*
     * Entry point: Builds DVFG for all methods in the analysis scene
     * Processes methods with available control flow graphs (CFGs)
     * 1. Performs intra-procedural analysis for each method
     * 2. Handles inter-procedural connections
     */
    public build(): void {
        // Phase 1: Intra-procedural analysis
        this.scene.getMethods().forEach(m => {
            if (m.getCfg()) {
                this.buildForSingleMethod(m);
                
                // add edge for throw
                this.processEdge4ThrowNode(m);
            }
        });

        // Phase 2: Inter-procedural analysis
        this.processInterProcedual();
    }

    /*
     * Core DVFG construction logic for a single method
     * Process:
     * 1. Creates formal parameter nodes
     * 2. Collects definition points
     * 3. Performs reaching definitions analysis
     * @param m Target method for DVFG construction
     */
    public buildForSingleMethod(m: ArkMethod): void {
        if (!m.getCfg()) {
            return;
        }

        // Create vfgNode for formal parameters
        this.addFormalParmVFGNodes(m);


        // Preprocess to collect variable definitions
        let defMap = this.preprocess(m);
        this.processMethodByReachingDef(m, defMap);
    }

    /*
     * Creates or connects DVFG nodes between definition and usage points
     * @param def - Definition statement
     * @param reach - Reaching statement where value is used
     * @param valOrRef - Value or field signature being tracked
     */
    private addNewNodes(def: Stmt, reach: Stmt, valOrRef: Value | FieldSignature): void {
        let srcNode = this.getOrNewDVFGNode(def, valOrRef);
        let dstNode = this.getOrNewDVFGNode(reach, valOrRef, def);

        this.dvfg.addDVFGEdge(srcNode, dstNode);
    }

    /*
     * Performs MOP analysis using reaching definitions and create DVFG Nodes
     * 1. Do MOP analysis
     * 2. Connect define and usage
     * 3. Handle global variable definitions
     * 4. Handle definitions without usages
     * @param m - Target method
     * @param defMap - Precomputed map of values to their definition statements
     */
    private processMethodByReachingDef(m: ArkMethod, defMap: Map<Value | FieldSignature, Set<Stmt>>): void {
        // Setup data flow analysis framework
        let problem = new ReachingDefProblem(m);
        let solver = new MFPDataFlowSolver();
        let solution = solver.calculateMopSolutionForwards(problem);
        let handledDefStmtNodes: SparseBitVector = new SparseBitVector();

        // Process analysis results to build DVFG edges
        solution.in.forEach((defs, reach) => {
            // Get current statement being analyzed
            const reachStmt = problem.flowGraph.getNode(reach);
            // Process all values used in the statement
            this.getStmtUsedValues(reachStmt).forEach(use => {
                let target: Value | FieldSignature = use;
                // Convert field refs to signatures for consistent lookup
                if (target instanceof AbstractFieldRef) {
                    target = target.getFieldSignature();
                }

                let defStmts = defMap.get(target);
                if (defStmts) {
                    // Connect definitions to their usages
                    defStmts.forEach(defStmt => {
                        let defId = problem.flowGraph.getNodeID(defStmt);
                        if (defs.test(defId)) {
                            this.addNewNodes(defStmt, reachStmt, target);
                            handledDefStmtNodes.set(defId);
                        }
                    });
                } else {
                    // it might has global variable
                    this.processStmtWithGlobal(m, target, reachStmt);
                }
            });
        });

        // find unhandled def stmt and add VFG Node for it
        this.processUnhandeledDefStmts(problem.flowGraph, handledDefStmtNodes);
    }

    private processStmtWithGlobal(m: ArkMethod, target: Value | FieldSignature, reachStmt: Stmt) {
        let globals = m.getBody()?.getUsedGlobals();
        // no Def is found, would be global var
        if (target instanceof Local && globals) {
            // TODO: get global from gloabls
            let dftMethod = m.getDeclaringArkFile().getDefaultClass().getDefaultArkMethod();
            let glbDefStmt = dftMethod?.getBody()?.getLocals()?.get(target.getName())?.getDeclaringStmt();
            if (glbDefStmt) {
                this.addNewNodes(glbDefStmt, reachStmt, target);
            }
        }
    }

    // Some def has no use, but still need add VFG node for it
    private processUnhandeledDefStmts(flowGraph: ReachingDefFlowGraph, handledDefStmtNodes: SparseBitVector) {
        for (let nodeID of flowGraph.getNodes()) {
            if (!handledDefStmtNodes.test(nodeID)) {
                let stmt = flowGraph.getNode(nodeID);

                if (stmt instanceof ArkReturnVoidStmt) {
                    this.addFormalReturnVFGNode(stmt);
                    continue;
                }

                let def = stmt.getDef();
                if (!def) {
                    continue;
                }

                this.getOrNewDVFGNode(stmt, def);
            }
        }
    }

    /*
     * Processes throw statements in the method and connects them to catch blocks
     * @param m - Target method
     */
    private processEdge4ThrowNode(m: ArkMethod): void {
        const cfg = m.getCfg();
        if (!cfg) return;

        for (const block of cfg.getBlocks()) {
            if (block.getHead() && block.getHead()!.getOriginalText() ===undefined) {
                continue;
            }
            for (const stmt of block.getStmts()) {
                if (stmt instanceof ArkThrowStmt) {
                    this.handleThrowStatement(stmt, block, m);
                }
            }
        }
    }

    private handleThrowStatement(stmt: ArkThrowStmt, block: BasicBlock, m: ArkMethod): void {
        const throwNodes: Set<DVFGNode> = this.getNodes4Stmt(stmt);
        const exceptionalSuccessorBlocks = block.getExceptionalSuccessorBlocks();
        if (exceptionalSuccessorBlocks && exceptionalSuccessorBlocks.length > 0) {
            this.connectToCatchBlocks(throwNodes, exceptionalSuccessorBlocks);
        } else {
            this.connectToInterCatchBlocks(throwNodes, m);
        }
    }

    private connectToCatchBlocks(throwNodes: Set<DVFGNode>, exceptionalSuccessorBlocks: BasicBlock[]): void {
        for (const exBlock of exceptionalSuccessorBlocks) {
            const catchStmt = exBlock.getHead()!;
            const catchNodes: Set<DVFGNode> = this.getNodes4Stmt(catchStmt);
            for (const throwNode of throwNodes) {
                for (const catchNode of catchNodes) {
                    this.dvfg.addDVFGEdge(throwNode, catchNode);
                }
            }
        }
    }

    private connectToInterCatchBlocks(throwNodes: Set<DVFGNode>, m: ArkMethod): void {
        const callsites = this.cg.getInvokeStmtByMethod(m.getSignature());
        for (const cs of callsites) {
            const catchStmts: Stmt[] = this.getCatchStmt4InterThrow(cs, cs.getCfg().getDeclaringMethod());
            for (const catchStmt of catchStmts) {
                const catchNodes: Set<DVFGNode> = this.getNodes4Stmt(catchStmt);
                for (const throwNode of throwNodes) {
                    for (const catchNode of catchNodes) {
                        this.dvfg.addDVFGEdge(throwNode, catchNode);
                    }
                }
            }
        }
    }

    /*
     * Preprocessing phase: Collects definition points in the method
     * @param m - Target method
     * @returns Map of values/fields to their defining statements
     */
    private preprocess(m: ArkMethod): Map<Value | FieldSignature, Set<Stmt>> {
        // Map definitions to their originating statements
        let defMap = new Map<Value | FieldSignature, Set<Stmt>>();
        // let globals = m.getBody()?.getUsedGlobals();
        let stmts = m.getCfg()!.getStmts();
        for (let s of stmts) {
            // Extract definition from statement
            let def: Value | FieldSignature | null = s.getDef();
            if (def != null) {
                // Normalize field references to signatures
                if (def instanceof AbstractFieldRef) {
                    def = def.getFieldSignature();
                }
                // Set or update definition-to-statements mapping
                let defStmts = defMap.get(def) ?? new Set<Stmt>();
                defStmts.add(s);
                defMap.set(def, defStmts);
            }
        }

        return defMap;
    }

    /*
     * Identifies values used by different statement types
     * @param stmt Statement to analyze
     * @returns List of values used by the statement
     */
    private getStmtUsedValues(stmt: Stmt): Value[] {
        // Handle different statement types and their value usages
        if (stmt instanceof ArkAssignStmt) {
            return this.getUsedValues(stmt.getRightOp());
        } else if (stmt instanceof ArkInvokeStmt) {
            return this.getUsedValues(stmt.getInvokeExpr());
        } else if (stmt instanceof ArkIfStmt) {
            return this.getUsedValues(stmt.getConditionExpr());
        } else if (stmt instanceof ArkReturnStmt) {
            return this.getUsedValues(stmt.getOp());
        } else if (stmt instanceof ArkThrowStmt) {
            return this.getUsedValues(stmt.getOp());
        } else if (stmt instanceof ArkReturnVoidStmt || stmt instanceof ArkAliasTypeDefineStmt || stmt instanceof DummyStmt) {
            return []; // No value usages for these statement types
        } else {
            throw new Error('unsupported stmt');
        }
    }

    /*
     * Recursively extracts used values from expressions
     * Handles nested expressions and method call arguments
     * @param val Value or expression to analyze
     * @returns Flat list of underlying value usages
     */
    private getUsedValues(val: Value): Value[] {
        if (val instanceof AbstractExpr) {
            // Handle different expression types
            if (val instanceof AbstractInvokeExpr) {
                // Process method call arguments
                return val.getArgs().flatMap(current => {
                    return this.getUsedValues(current);
                }, []);
            } else {
                // Generic expression processing
                return val.getUses().flatMap(current => {
                    return this.getUsedValues(current);
                }, []);
            }
        }
        if (val instanceof Constant) {
            return []; // Constants don't represent value usages
        } else if (val.getType() instanceof LexicalEnvType) {
            // handle closre
            return [...(val.getType() as LexicalEnvType).getClosures()];
        // } else if (val instanceof ClosureFieldRef) {
        //     return [val.getBase()];
        }
        return [val]; // Base case: simple value usage
    }

    /*
     * Gets or creates DVFG node for a statement
     * @param stmt - Associated statement
     * @param valOrRef - Value or field being tracked
     * @param defStmt - For parameter nodes in call statements
     * @returns Appropriate DVFG node type based on statement context
     */
    public getOrNewDVFGNode(stmt: Stmt, valOrRef: Value | FieldSignature, defStmt: Stmt | undefined = undefined): DVFGNode {
        let nodeIDs = this.dvfg.getVFGNodeIDByStmtOrFP(stmt);
        if (nodeIDs) {
            for (let nodeID of nodeIDs) {
                // not Invoke Expr, it should be 1:1 map
                if (!stmt.getInvokeExpr()) {
                    return this.dvfg.getNode(nodeID)! as DVFGNode;
                } else {
                    // ActuralParam has 1:n
                    let node = this.dvfg.getNode(nodeID)! as DVFGNode;
                    if (node instanceof ActualParmVFGNode && node.getDefStmt() === defStmt) {
                        return node;
                    }
                }
            }
        }

        let node: DVFGNode;
        if (stmt instanceof ArkAssignStmt && !(stmt.getRightOp() instanceof AbstractInvokeExpr)) {
            node = this.addAssignVFGNode(stmt);
        } else if (stmt instanceof ArkIfStmt) {
            node = this.addCmpVFGNode(stmt);
        } else if (stmt instanceof ArkInvokeStmt || (stmt instanceof ArkAssignStmt && stmt.getRightOp() instanceof AbstractInvokeExpr)) {
            if (defStmt) {
                node = this.addActualParmVFGNode(valOrRef, defStmt!, stmt);
            } else {
                node = this.addActualRetVFGNode(stmt);
            }
        } else if (stmt instanceof ArkReturnStmt || stmt instanceof ArkReturnVoidStmt) {
            node = this.addFormalReturnVFGNode(stmt);
        } else if (stmt instanceof ArkThrowStmt) {
            node = this.addThrowVFGNode(stmt);
        } else {
            node = this.addNormalVFGNode(stmt);
        }

        return node;
    }

    private addAssignVFGNode(stmt: ArkAssignStmt): AssignVFGNode {
        let id: NodeID = this.dvfg.getNodeNum();
        let kind: VFGNodeKind;
        let vfgNode: AssignVFGNode;
        if (stmtIsCreateAddressObj(stmt)) {
            vfgNode = new AddrObjVFGNode(id, stmt);
        } else if (stmtIsCopyKind(stmt)) {
            vfgNode = new CopyVFGNode(id, stmt);
            let rop = stmt.getRightOp();

            // Collect FormalParmNode
            if (rop instanceof ArkParameterRef) {
                let fpNode = this.dvfg.getVFGNodeIDByStmtOrFP(rop);
                if (!fpNode) {
                    throw new Error('DVFG builder: cannot find formalParm Node');
                } else if (rop.getType() instanceof LexicalEnvType) {
                    for (const id of fpNode) {
                        const closureParmNode: FormalClosureParmVFGNode = this.dvfg.getNode(id) as FormalClosureParmVFGNode;
                        let idx = 0;
                        let declaringStmt: Stmt | undefined = undefined;
                        for (const fstmt of stmt.getCfg().getStartingBlock()?.getStmts()!) {
                            if (fstmt instanceof ArkAssignStmt && fstmt.getRightOp() instanceof ClosureFieldRef) {
                                if (idx === closureParmNode.getClosureIdx()) {
                                    declaringStmt = fstmt;
                                    break;
                                }
                                idx++;
                            }
                        }
                        if (!declaringStmt) {
                            throw new Error('DVFG builder: cannot find formalClosureParm declaringstmt');
                        }
                        const declaringNodeID = this.dvfg.getVFGNodeIDByStmtOrFP(declaringStmt);
                        if (!declaringNodeID || declaringNodeID.count() !== 1) {
                            throw new Error('DVFG builder: cannot find formalClosureParm');
                        }
                        this.dvfg.addDVFGEdge(closureParmNode, this.dvfg.getNode(declaringNodeID.findFirst()) as DVFGNode);
                    }
                } else if (fpNode.count() === 1) {
                    let nodeID = fpNode.findFirst();
                this.dvfg.addDVFGEdge(this.dvfg.getNode(nodeID)! as DVFGNode, vfgNode);
                }

                
            }
        } else if (stmtIsReadKind(stmt)) {
            vfgNode = new LoadVFGNode(id, stmt);
        } else if (stmtIsWriteKind(stmt)) {
            vfgNode = new WriteVFGNode(id, stmt);
        } else {
            kind = VFGNodeKind.Assignment;
            vfgNode = new AssignVFGNode(id, kind, stmt);
        }

        this.dvfg.addDVFGNode(vfgNode, stmt);
        return vfgNode;
    }

    private addNormalVFGNode(stmt: Stmt): DVFGNode {
        let id: NodeID = this.dvfg.getNodeNum();
        let kind = VFGNodeKind.Normal;
        let dvfgNode = new DVFGNode(id, kind, stmt);

        this.dvfg.addDVFGNode(dvfgNode, stmt);
        return dvfgNode;
    }

    private addCmpVFGNode(stmt: Stmt): CmpVFGNode {
        let id: NodeID = this.dvfg.getNodeNum();
        let dvfgNode = new CmpVFGNode(id, stmt);

        this.dvfg.addDVFGNode(dvfgNode, stmt);
        return dvfgNode;
    }

    private addActualRetVFGNode(stmt: Stmt): ActualReturnVFGNode {
        let id: NodeID = this.dvfg.getNodeNum();
        let dvfgNode = new ActualReturnVFGNode(id, stmt);

        this.dvfg.addDVFGNode(dvfgNode, stmt);
        return dvfgNode;
    }

    private addActualParmVFGNode(valOrRef: Value | FieldSignature, def: Stmt, reach: Stmt): DVFGNode {
        let id: NodeID = this.dvfg.getNodeNum();
        let expr = reach.getInvokeExpr();
        if (!expr) {
            throw new Error('DVFG Builder: can not get invoke expr');
        }

        let loc = 0;
        let closureIdx = -1;
        for (const arg of expr.getArgs()) {
            if (arg.getType() instanceof LexicalEnvType && valOrRef instanceof Local) {
                const closureLocals = (arg.getType() as LexicalEnvType).getClosures();
                for (let i = 0; i < closureLocals.length; i++) {
                    if (closureLocals[i] === valOrRef) {
                        closureIdx = i;
                        break;
                    }
                }
                if (closureIdx !== -1) {
                    break;
                }
            }
            if (arg === valOrRef) {
                break;
            }
            loc++;
        }
        let dvfgNode: ActualParmVFGNode;
        if (closureIdx === -1) {
            dvfgNode = new ActualParmVFGNode(id, reach, def, loc);
        } else {
            dvfgNode = new ActualClosureParmVFGNode(id, reach, def, loc, closureIdx);
        }

        this.dvfg.addDVFGNode(dvfgNode, reach);
        return dvfgNode;
    }

    private addFormalReturnVFGNode(stmt: ArkReturnStmt | ArkReturnVoidStmt): FormalReturnVFGNode {
        const id = this.dvfg.getNodeNum();
        const node = new FormalReturnVFGNode(id, stmt);
        this.dvfg.addDVFGNode(node, stmt);
        let m = stmt.getCfg().getDeclaringMethod();
        let cgNode = this.cg.getCallGraphNodeByMethod(m.getSignature());
        let returnNodes = this.method2ReturnNodeMap.get(cgNode.getID()) || new SparseBitVector(32);
        returnNodes.set(id);
        this.method2ReturnNodeMap.set(cgNode.getID(), returnNodes);
        return node;
    }

    private addFormalParmVFGNodes(m: ArkMethod): void {
        let fps = m.getParameterRefs();
        if (!fps || fps.length === 0) {
            return;
        }

        for (let i = 0; i < fps.length; i++) {
            let fp = fps[i];
            if (fp.getType() instanceof LexicalEnvType) {
                for (let i = 0; i < (fp.getType() as LexicalEnvType).getClosures().length; i++) {
                    let id = this.dvfg.getNodeNum();
                    let dvfgNode = new FormalClosureParmVFGNode(id, fp, i);
                    this.dvfg.addDVFGNode(dvfgNode, fp);
                }
            } else {
                let id: NodeID = this.dvfg.getNodeNum();
                let dvfgNode = new FormalParmVFGNode(id, fp, i);
                this.dvfg.addDVFGNode(dvfgNode, fp);
            }
        }
    }

     /*
     * Handles inter-procedural data flow connections
     * 1. Connects actual parameters to formal parameters
     * 2. Connects return values between callers and callees
     */
    private processInterProcedual(): void {
        for (let m of this.scene.getMethods()) {
            let callsites = this.cg.getCallSitesByMethod(this.cg.getCallGraphNodeByMethod(m.getSignature()).getID());
            for (let cs of callsites) {
                this.handleArguments(cs);
                this.handleReturnValues(cs);
            }
        }
    }

    private handleArguments(cs: CallSite): void {
        let callee = this.cg.getMethodByFuncID(cs.calleeFuncID);
        if (!callee) {
            return;
        }

        let formalParms = this.scene.getMethod(callee)?.getParameterRefs();
        if (!formalParms) {
            return;
        }

        let ivkStmt = cs.callStmt;
        if (ivkStmt.getInvokeExpr()?.getMethodSignature().toString() !== callee.toString()) {
            return;
        }


        let vfgNodes = this.dvfg.getVFGNodeIDByStmtOrFP(ivkStmt);
        if (!vfgNodes) {
            return;
        }

        for (let nodeID of vfgNodes) {
            let src = this.dvfg.getNode(nodeID) as ActualParmVFGNode;
            if (!src || !(src instanceof ActualParmVFGNode)) {
                continue;
            }

            let actualParmIdx = src.getParmLoc();
            let formalParm = formalParms[actualParmIdx];
            if (!formalParm) {
                throw new Error('VFG builder: error to get formal Parm');
            }

            let nodes = this.dvfg.getVFGNodeIDByStmtOrFP(formalParm);
            if (!nodes) {
                throw new Error('VFG builder: error to get FormalParmVFGNode');
            } else if (formalParm.getType() instanceof LexicalEnvType) {
                for (const nodeID of nodes) {
                    const dst = this.dvfg.getNode(nodeID) as FormalClosureParmVFGNode;
                    if (dst.getClosureIdx() === (src as ActualClosureParmVFGNode).getClosureIdx()) {
                        this.dvfg.addDVFGEdge(src, dst, VFGEdgeKind.CallDirect);
                    }
                }
            } else if (nodes.count() === 1) {
                let dst = this.dvfg.getNode(nodes.findFirst()) as DVFGNode;
            if (!dst) {
                throw new Error('VFG builder: error to get node');
            }
            this.dvfg.addDVFGEdge(src, dst, VFGEdgeKind.CallDirect);
            } else {
                throw new Error('VFG builder: error to get FormalParmVFGNode');
            }

            
        }
    }

    private handleReturnValues(cs: CallSite): void {
        const ivkStmt = cs.callStmt;
        let actRetNode: ActualReturnVFGNode;
        if (ivkStmt instanceof ArkAssignStmt) {
            actRetNode = this.getActualRetVFGNode(ivkStmt);
        } else {
            return;
        }

        let formalRetNodes = this.method2ReturnNodeMap.get(cs.calleeFuncID);
        if (!formalRetNodes) {
            throw new Error('DVFG Builder: has ActualRetVFGNode, but no  FormalRetVFGNode');
        }

        for (let frNodeID of formalRetNodes) {
            this.dvfg.addDVFGEdge(this.dvfg.getNode(frNodeID) as DVFGNode, actRetNode, VFGEdgeKind.RetDirect);
        }
    }

    private getActualRetVFGNode(stmt: Stmt): ActualReturnVFGNode {
        let nodes = this.dvfg.getVFGNodeIDByStmtOrFP(stmt);
        if (!nodes) {
            throw new Error('Cannot get ActualRetNode');
        }

        // Normally only one ActRetNode should be found
        for (let nodeID of nodes) {
            let node = this.dvfg.getNode(nodeID) as DVFGNode;
            if (node instanceof ActualReturnVFGNode) {
                return node;
            }
        }

        // If no node found, there is a exception
        throw new Error('Cannot get ActualRetNode');
    }

    private addThrowVFGNode(stmt: ArkThrowStmt): ThrowVFGNode {
        let id: NodeID = this.dvfg.getNodeNum();
        let vfgNode = new ThrowVFGNode(id, stmt);
        this.dvfg.addDVFGNode(vfgNode, stmt);
        return vfgNode;
    }

    private getNodes4Stmt(stmt: Stmt): Set<DVFGNode> {
        const ids = this.dvfg.getVFGNodeIDByStmtOrFP(stmt);
        const nodes: Set<DVFGNode> = new Set();
        if (ids === undefined) {
            return nodes;
        }
        for (const id of ids) {
            const throwNode = this.dvfg.getNode(id);
            if (throwNode instanceof DVFGNode) {
                nodes.add(throwNode);
            }
        }
        return nodes;
    }

    private getCatchStmt4InterThrow(stmt: Stmt, mtd: ArkMethod): Stmt[] {
        const traps = mtd.getBody()?.getTraps();
        if (!traps) {
            return this.getCatchStmtFromCallers(stmt, mtd);
        }

        for (const trap of traps) {
            if (this.isStmtInTryBlock(stmt, trap)) {
                const catchStmt = this.getFirstCatchStmt(trap);
                if (catchStmt) {
                    return [catchStmt];
                }
            }
        }

        return this.getCatchStmtFromCallers(stmt, mtd);
    }

    /*
    * 判断给定的语句是否在某个 Trap 的 try 块中
    */
    private isStmtInTryBlock(stmt: Stmt, trap: Trap): boolean {
        return trap.getTryBlocks().some(tryBlk => tryBlk.getStmts().includes(stmt));
    }

    /*
    * 获取 Trap 的第一个 catch 块中的语句
    */
    private getFirstCatchStmt(trap: Trap): Stmt | undefined {
        for (const catchBlk of trap.getCatchBlocks()) {
            const catchStmt = catchBlk.getStmts()[0];
            if (catchStmt) {
                return catchStmt;
            }
        }
        return undefined;
    }

    /*
    * 从调用者中递归查找 catch 语句
    */
    private getCatchStmtFromCallers(stmt: Stmt, mtd: ArkMethod): Stmt[] {
        const callsites = this.cg.getInvokeStmtByMethod(mtd.getSignature());
        const result: Stmt[] = [];

        for (const cs of callsites) {
            const caller = cs.getCfg()?.getDeclaringMethod();
            if (!caller || !caller.getCfg()) {
                continue;
            }

            const catchStmts = this.getCatchStmt4InterThrow(cs, caller);
            if (catchStmts.length > 0) {
                result.push(...catchStmts);
            }
        }

        return result;
    }

}